/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Bank;
import Household.Household;
import MortgageCore.*;
import java.util.*;
import simframe.simulation.*;
import simframe.util.*;
import generators.BankGenerator;
import generators.BankGenerator.BankInfo;

/**
 *
 * @author karakots
 */
public class Bank extends SimObject implements iDepository, iBuyerSeller, iLender, iGoverned  {

    //
    // instance variables
    //

    // for scaling this bank to the real data
    private static double bankScaleFactor;
    private static double totalDeposits = 0;
    private double myDepositsThreshold;
    private double myPctOfDepositsInSim;
    private double myPctThreshold;

    // general sim object state
    int myDayOfMonth;           // day of month this household will be evaluated
    int dayOfMonth;
    static final int kDaysPerMonth = 30;
    static final int kDaysPerQuarter = 90;
    private final Location loc;
    public String name;
    public boolean nameChanged;
    private static int bankNumber = 0;
    private BankInfo info;
    
    // General Bank State
    private double reserveRate;         // percent of deposits required to have on reserve
    private double intialEquity;        // amount of cash the bank starts with
    private double rateSpreadScale;
    private double savingsMonthlyInterestRate;
    private BankBooks myBooks;
    // for managing risk of too many loan offers out
    private double loanBuffer;
    private double maxLoanShortgae;
    private int quarterCounter;         // keeps track of 90-day intervals
    private boolean failed;
    private BankMarket myBankMarket;
    private Bank myAcquirer;
    private boolean hasAdapted;

    // savings
    public static class DepositoryAccount {

        iDepositor depositor;
        int accountNumber;
        double balance;
        double interestRate;

        void close() {
            balance = 0;
            depositor = null;
        }
    }
    private List<DepositoryAccount> savingsAccounts = new ArrayList<DepositoryAccount>();
    private int maxSavingsAccountNumberAssigned = -1;
    static final int kOpenAccountFailed = -1;

    // lending
//    public static class BorrowerAccount {
//        iBuyerSeller borrower;
//        int accountNumber;
//        Mortgage mortgage;
//        int numMissedPayments;
//    }
    
    final private List<Mortgage> mortgages = new ArrayList<Mortgage>();
    private int maxBorrowerAccountNumberAssigned = 0;
    private LendingPolicy myLP = new LendingPolicy(this);
    MortgageMarket mortgageMarket;
    // for managaing adaptation of lending policy
    private int loanAdaptCounter;
    private int daysBetweenLoanAdapt;

    public List<String> policyRecord;
    private boolean logData;
    private boolean enableLogData;
    private boolean logViewOpen;


    //
    // accessors
    //
    
    public void setLogData(boolean b) {
         if (enableLogData) {
             logData = b;
             if (logData && (policyRecord == null)) {
                 policyRecord = new ArrayList<String>();
             }
         }
    }
    public void setLogViewOpen(boolean b)               {logViewOpen = b;}
    public boolean getLogViewOpen()                     {return logViewOpen;}
    public void setEnableLogData(boolean b)             {enableLogData = b;}
    public boolean getEnableLogData()                   {return enableLogData;}
    public boolean getLogData()                         {return logData;}
    public boolean getSelected()                        {return isSelected;}
    public void setSelected(boolean selected)           {isSelected = selected;}
    public Location getLoc()                            {return loc;}
    public double getCashAssets()                       {return myBooks.getCashBalance();}
    public boolean getRemotelySelected()                {return (remotelySelectedCount > 0);}
    public double getReserveRequired()                  {return myBooks.getReserveRequired();}
    public double getSavingsMonthlyInterestRate()       {return savingsMonthlyInterestRate;}
    public void setSavingsMonthlyInterestRate(double r) {savingsMonthlyInterestRate = r;}
    public double getNumDefaults()                      {return myBooks.getNumDefaults();}
    public double getAmountAvialableToLoanExcludingOffers()            {return myBooks.getAmountAvialableToLoanExcludingOffers();}
    public void setMortgageMarket(MortgageMarket m)     {mortgageMarket = m;}
    public LendingPolicy getLendingPolicy()             {return myLP;}
    public double getLoanBalanceOutstanding()           {return myBooks.getLoanBalanceOutstanding();}
    public double getEquity()                           {return myBooks.getEquity();}
    public double getLoansPreapproved()                 {return myBooks.getLoanAmountPreapproved();}
    public double getNumLoansCurrentlyFunded()          {return myBooks.getNumLoansFunded();}
    public double getNumLoansEverFunded()               {return myLP.getNumLoansEverFunded();}
    public double getNumLoansEverOffered()              {return myLP.getNumloansEverOffered();}
    public double getNumLoansEverPreapproved()          {return myLP.getNumLoansEverPreapproved();}
    public double getDeposits()                         {return myBooks.getDeposits();}
    public double getNumLoansPaidOff()                  {return myLP.getNumLoansPaidOff();}
    public double getNumDefaultsEver()                  {return myLP.getNumDefaultsEver();}
    public double getNumLoanApplications()              {return myLP.getNumLoanApplications();}
    public double getReservaRate()                      {return reserveRate;}
    public boolean getFailed()                          {return failed;}
    public void setBankMarket(BankMarket bm)            {myBankMarket = bm;}
    public double getLastQtrCash()                      {return myBooks.getLastQtrCash();}
    public double getLastQtrDeposits()                  {return myBooks.getLastQtrDeposits();}
    public double getLastQtrLoanBalanceOutstanding()    {return myBooks.getLastQtrLoanBalanceOutstanding();}
    public double getLastQtrReserveRequired()           {return myBooks.getLastQtrReserveRequired(); }
    public double getLastQtrLoansAmountPreApproved()    {return myBooks.getLastQtrLoansAmountPreApproved();}
    public double getNumLoansOfferedThisQtr()           {return myLP.getNumLoansOfferedThisQtr();}
    public double getNumLoansPreapprovedThisQtr()       {return myLP.getNumLoansPreapprovedThisQtr();}
    public double getNumLoansPaidOffThisQtr()           {return myLP.getNumLoansPaidOffThisQtr();}
    public double getNumLoanApplicationsThisQtr()       {return myLP.getNumLoanApplicationsThisQtr();}
    public double getNumLoansFundedThisQtr()            {return myBooks.getNumLoansFundedThisQtr();}
    public double getNumDefaultsThisQtr()               {return myBooks.getNumDefaultsThisQtr();}
    public double getLastQtrAmountAvialableToLoan()     {return myBooks.getLastQtrAmountAvialableToLoan();}
    public double getLastQtrEquity()                    {return myBooks.getLastQtrEquity();}
    public Bank getAcquirer()                           {return myAcquirer;}
    public String getName()                             {return name;}
    public double getPctLoanAppsQualifying()            {return myLP.pctLoanAppsQualifying();}
    public double getPctLoanAppsQualifyingThisQtr()     {return myLP.pctLoanAppsQualifyingThisQtr();}
    public void setBankScaleFactor(double sf)           {bankScaleFactor = sf;}
    public double getMyPctThreshold()                   {return myPctThreshold;}
    public boolean getHasAdapted()                      {return hasAdapted;}
    public void setHasAdapted(boolean b)                {hasAdapted = b;}

    public double getAverageLoanSize() {
        double numFunded = getNumLoansEverFunded();
        if (numFunded == 0)
            return 0;
        else
            return myBooks.getAmountLoansEverFunded() / numFunded;
    }


    public double getAverageLoanSizeThisQtr() {
        double numFunded = getNumLoansFundedThisQtr();
        if (numFunded == 0)
            return 0;
        else
            return myBooks.getAmountLoansFundedThisQtr() / numFunded;
    }

    public void setName(String n) {
        name = n;
        nameChanged = true;
    }

    public void     setRemotelySelected(boolean selected) {
        if (selected)   remotelySelectedCount += 1;
        else            remotelySelectedCount -= 1;
    }

   public String getDate() {
         long day = getTime() / simframe.simulation.Time.Day;
         int year = 1991 + (int) day/360;
         int dayOfYear = (int) day % 360;
         int month = (dayOfYear / 30) + 1;
         String monthName;
         switch (month) {
             case 1:        monthName = "Jan ";  break;
             case 2:        monthName = "Feb ";  break;
             case 3:        monthName = "Mar ";  break;
             case 4:        monthName = "Apr ";  break;
             case 5:        monthName = "May ";  break;
             case 6:        monthName = "Jun ";  break;
             case 7:        monthName = "Jul ";  break;
             case 8:        monthName = "Aug ";  break;
             case 9:        monthName = "Sep ";  break;
             case 10:       monthName = "Oct ";  break;
             case 11:       monthName = "Nov ";  break;
             case 12:       monthName = "Dec ";  break;
             default:       monthName = "??";   break;
         }
         int dayOfMonth = dayOfYear - ((month-1) * 30) + 1;
         String date = new String(monthName + dayOfMonth + ", " + year + ": ");
         return date;
    }


    // UI State
    boolean isSelected;
    public int remotelySelectedCount; // selected by another component
    public boolean paramViewOpen;

    private static BankGenerator Generator;
    public static void InitializeGenerator()
     {
         if (Generator == null) {
             try {
                 Generator = new BankGenerator();
             } catch (java.io.IOException e) {
             }
         }
     }

    public boolean servesBorrowerState(Household h) {
        if (info.states.containsKey(h.getStateName()))
            return true;
        else
            return false;
    }



    //
    // General Bank Methods
    //
    public Bank(boolean isAdaptive)
    {
        InitializeGenerator();
        info = Generator.GetNext();

        name = info.name;
        totalDeposits += info.deposits;
        myDepositsThreshold = totalDeposits;
        hasAdapted = false;

        // Gener Sim Object
        double x = info.headquarters.GetLongitude();
        double y = info.headquarters.GetLatitude();
        loc = new Location(x, y);
       
        myDayOfMonth = 1 + (int)(Math.random() * kDaysPerMonth);
        dayOfMonth = 0;
        bankNumber++;
        //name = new String("Bank " + (int) lc.getX() + ", " + (int) lc.getY());
        //name = new String("Bank " + bankNumber);
        nameChanged = false;

        // General bank
        myBooks = new BankBooks();
        myBooks.setMyBank(this);

        reset();
        reserveRate = 0.06;     // start with 6% reserve rate
        rateSpreadScale = 1.0;

        // savings
        //savingsMonthlyInterestRate = 0.005 * Math.random(); // quarter percent per month
        savingsMonthlyInterestRate = 0.003 + (0.002 * Math.random()); // quarter percent per month

        // loans
        defineLendingPolicy(myLP, isAdaptive);

        // UI
        isSelected = false;
        remotelySelectedCount = 0;
        paramViewOpen = false;
        logViewOpen = false;
    }

    public void setUpFinances() {
        // the bank info has actual assets, deposits and equity
        // these are in $K
        // we need to scale these numbers to the number of simulated households
        //intialEquity = 2000000 + 5000000 * Math.random();
        intialEquity = 1000 * (info.equity / bankScaleFactor);
        myPctOfDepositsInSim = info.deposits / totalDeposits;
        myPctThreshold = myDepositsThreshold / totalDeposits;
    }

    //
    // SimObject Inerface
    //
    public boolean dead() {
        return false;
    }

    public void reset() {
        myBooks.reset();
        myBooks.transaction(intialEquity, BankBooks.Transaction.EQUITY);
        dayOfMonth = 0;
        myLP.reset();

        for (DepositoryAccount acc : savingsAccounts) {
            acc.balance = 0;
        }
        loanBuffer = 1;
        maxLoanShortgae = 0;
        daysBetweenLoanAdapt = 360;
        loanAdaptCounter = (int) (daysBetweenLoanAdapt * Math.random());
        quarterCounter = 0;
        failed = false;
        myAcquirer = null;
    }

    private void newDay() {
        if (dayOfMonth++ >= kDaysPerMonth)
           dayOfMonth = 1;
        if (failed)
            return;

        myBooks.ComputeReserveRequirement();  // I think this is unnecessary
        checkForBankFailure();
        if ( myLP.getAdaptive() && (++loanAdaptCounter >= daysBetweenLoanAdapt)) {
            myLP.adapt();
            loanAdaptCounter = 0;
        }
        if (quarterCounter++ > kDaysPerQuarter) {
            quarterCounter = 1;
            myBooks.newQuarter();
            myLP.newQuarter();
        }

    }

    private void checkForBankFailure() {
        // if cash < reserves
        //if (Math.random() < .0001) {  // uncomment this line to test bank failure code
        if (myBooks.getFailed()) {
            if (!failed) {
                // modify the bank name to trigger red display in bank summary
                name = name.concat(" X");
            }
            failed = true;
            // find the closest healthy bank
            Bank acquirer = myBankMarket.getGoodBigBank(this);
            if (acquirer != null) {
                // give that bank deposits
                moveDepositsTo(acquirer);
                // and loans
                moveLoansTo(acquirer);
                // and cash
                moveCashTo(acquirer);
                // we'll want to not give over defaulted loans, eventually
                myBooks.transaction(0, BankBooks.Transaction.CLOSE);
                myAcquirer = acquirer;
            }
        }
    }

    private void moveCashTo(Bank dest) {
        double cash = myBooks.getCashBalance();
        dest.myBooks.transaction(cash, BankBooks.Transaction.EQUITY);
        myBooks.transaction(-cash, BankBooks.Transaction.EQUITY);
    }

    private void moveLoansTo(Bank dest) {

        synchronized (mortgages) {
            // for each mortgage
            for (Mortgage m : mortgages) {
                double balance = m.getDebtOwed();
                dest.addMortgage(m);
                // tell the associated mortgage the new bank and account number

                m.setBank(dest);
                // delete the local account
                myBooks.transaction(balance, BankBooks.Transaction.MORTGAGESOLD);
            }

            mortgages.clear();
        }
    }

    private void moveDepositsTo(Bank dest) {
        // for each account
        int oldAccountNum = 0;
        for (DepositoryAccount d : savingsAccounts) {
            // create an account at the destination bank
            // move the cash
            double balance = d.balance;
            iDepositor depositor = d.depositor;
            int newAccountNum = dest.openAccount(depositor, balance);
            // tell the associated household the new bank and account number
            depositor.savingsAccountMoved(oldAccountNum, this, newAccountNum, dest);
            // delete the local account
            this.closeAccount(oldAccountNum);
            // deposits are separate from cash, so we need to ad the cash back in here
            myBooks.transaction(balance, BankBooks.Transaction.EQUITY);
            oldAccountNum++;
        }
    }

    // if cash drops below reserve requirement, we may have offered too many loans
    // to decrease this situation, we can keep a larger buffer before offering more loans.
    private boolean throttleLoanOffers() {
        double amtAvailable = myBooks.getAmountAvialableToLoanExcludingOffers();
        if (amtAvailable < 0) {
            if (amtAvailable < maxLoanShortgae) {
                loanBuffer++;
                maxLoanShortgae = amtAvailable;
            }
            return true;
        } else {
            //    maxLoanShortgae = 0;
            return false;
        }
    }

    public void updateRateSpreadScale(double rate) {
        rateSpreadScale = rate;
        setMortgageInterestRate(myLP);
    }

    void setMortgageInterestRate(LendingPolicy myLendingpolicy) {
        myLendingpolicy.monthlyMortgageInterestRate =  savingsMonthlyInterestRate +
                (myLendingpolicy.monthlyRateSpread)*rateSpreadScale;
    }


    //
    // lending
    //
    public void addMortgage(Mortgage mortgage) {
        if (mortgage == null) {
            return;
        }

        synchronized (mortgages) {
            mortgages.add(mortgage);
        }
    }

    private void defineLendingPolicy(LendingPolicy myLendingpolicy, boolean isAdaptive) {
        myLendingpolicy.monthlyRateSpread = (.1 * Math.random())/12;
        setMortgageInterestRate(myLendingpolicy);
        myLendingpolicy.minMortgagePctDown = Math.random() * 0.3;
        myLendingpolicy.minLoanSize = 0; //20000 + 100000 * Math.random();
        myLendingpolicy.mortgagePoints = .02* Math.random();
        myLendingpolicy.maxPaymentToIncome = 0.25 + (0.2 * Math.random());
        myLendingpolicy.maxLoanSize = 250000 + (1500000 * Math.random());
        //myLendingpolicy.maxLoanSize = (10 + (Math.random() * 10)) * (20000 + 200000 * Math.random()); //myLendingpolicy.minLoanSize;

        myLendingpolicy.setAdaptive(isAdaptive);

        // type of loans this bank offers
        if (Math.random() < 0.5)
            myLendingpolicy.fixedRateLoans = true;
        else
            myLendingpolicy.fixedRateLoans = true;

        // length of loans this bank offers
        myLendingpolicy.loanLength30years = true;
            /*
        if (Math.random() < 0.5) {
            myLendingpolicy.loanLength15years = true;
            if (Math.random() < 0.5)
                myLendingpolicy.loanLength30years = true;
            else
                myLendingpolicy.loanLength30years = false;
        }
        else {
            myLendingpolicy.loanLength30years = true;
            if (Math.random() < 0.5)
                myLendingpolicy.loanLength15years = true;
            else
                myLendingpolicy.loanLength15years = false;
        }
            */
    }

//    private double getPresentValueOfLoans() {
//        // pv = C/i * (1-1/pow((1+i),n))
//        // source: http://en.wikipedia.org/wiki/Present_value
//        double pv = 0;
//
//        for(BorrowerAccount b : mortgages) {
//            Mortgage mortgage = b.mortgage;
//            if ((mortgage != null) && mortgage.isFunded()) {
//                double c = mortgage.getMonthlyPayment();
//                double i = mortgage.getTerms().getMonthlyInterestRate();
//                double n = (mortgage.getTerms().getLengthInYears() * 12) - mortgage.getNumPayments();
//                double locPV = c / i * (1 - 1 / Math.pow((1 + i), n));
//                pv += locPV;
//            }
//        }
//        return pv;
//    }

    public void offerMortgages() {
        // decide how many mortgages types to off
        // decide how much money to allocate to each type
        // decide how many mortages of each type to offer
        // we'll start with one per day, as long as we have capital to offer them
        if (!myLP.loanLength30years && !myLP.loanLength15years)
            return; // we're not ofering any mortgages today
        int numLoans =  (int) (myBooks.getAmountAvialableToLoanExcludingOffers() / (myLP.maxLoanSize));
        if (numLoans > 2)
            numLoans *= 0.9;

        for (int ln = 0; ln < numLoans; ln++) {
        //if (myBooks.getAmountAvialableToLoanExcludingOffers() > (myLP.maxLoanSize * loanBuffer)) {
            MortgageOffer offer = new MortgageOffer();
            setMortgageTerms(offer.getMortgageTerms());

            offer.setType(MortgageOffer.Type.Ask);
            offer.setIsActiveOffer(true);
            offer.setDaysOfferValid(30);
            offer.setAgent(this);
            mortgageMarket.addOffer(offer);
            myLP.addOffer();
        }
    }

    private void setMortgageTerms(MortgageTerms mt) {
        mt.setMonthlyInterestRate(myLP.monthlyMortgageInterestRate);
        mt.setIsFixRate(myLP.fixedRateLoans);

        if (myLP.loanLength15years && myLP.loanLength30years) {
            if (Math.random() < 0.5)
                mt.setLengthInYears(15);
            else
                mt.setLengthInYears(30);
        } else if (myLP.loanLength15years) {
            mt.setLengthInYears(15);
        } else if (myLP.loanLength30years) {
            mt.setLengthInYears(30);
        }

        mt.setPoints(myLP.mortgagePoints);
        mt.setMinDownPayment(myLP.minMortgagePctDown);
    }

    public void agePreapprovedMortgages() {
        // go throught the list of mortgages that have been preapproved
        //for (BorrowerAccount b : mortgages) {
        final List<Mortgage> inValid = new ArrayList<Mortgage>();
        inValid.clear();

        synchronized (mortgages) {
            for (Mortgage mortgage : mortgages) {
                if ((mortgage.getPreapproved()) && !(mortgage.ageMortgage())) {
                    Household hh;
                    myBooks.transaction(mortgage.getPreapprovedAmount(), BankBooks.Transaction.LOANPREAPPROVALEXPIRED);
                    mortgage.borrowerMortgageExpired();
                }
            }

            mortgages.removeAll(inValid);
        }
    }

    // allows mortgage market to score loan requests against loan offers
    public boolean rateBorrowerForMortgage(MortgageOffer bid, MortgageOffer myAsk,
            double borrowerAssets, double borrowerMonthlyIncome, Household borrower, double borrowerAmtDesired) {
        // if I can reject the borrower out of hand, don;t go into the loop

        // check loan size
        if (borrowerAmtDesired > myLP.maxLoanSize) {
            myLP.rejectLoanTooLarge();
            return false;
        }

        double i = myAsk.getMortgageTerms().getMonthlyInterestRate();
        double n = (myAsk.getMortgageTerms().getLengthInYears())*12;
        double estimatedLoanPayment = getMonthlyPayment(i, n, borrowerAmtDesired);
        double cashForDownPayment =  borrowerAssets - (estimatedLoanPayment);

        // Percent Down Required
        double points = borrowerAmtDesired * myLP.mortgagePoints;
        double pctDown = (cashForDownPayment-points)/borrowerAmtDesired;
        if (pctDown < myLP.minMortgagePctDown) {
            myLP.rejectPctDown();
            return false;
        }

        // use the ratio of payment to income
        double ratio = estimatedLoanPayment / borrowerMonthlyIncome;
        if (ratio > myLP.maxPaymentToIncome) {
            myLP.rejectLTI();
            return false;
        }

        return true;
    }

    public double getMonthlyPayment(double i, double n, double p) {
        double nextPayment;
        // formula from http://en.wikipedia.org/wiki/Amortization_calculator
        //payment = (p*i) /(1 - Math.pow((1+i),-n));
        // i = monthly interest rate, n = number of months
        if (i > 0)
            nextPayment = (p * i) / (1 - Math.pow(1 / (1 + i), n));
        else
            nextPayment = p / n;
        return nextPayment;
    }

    public double getAmountAtPayment(double i, double n, double payment) {
        double principle;
        if (i > 0)
            principle = (payment * (1 - Math.pow(1 / (1 + i), n)))/i;
        else
            principle = payment * n;
        return principle;
    }

    public synchronized void offerExpired(MortgageOffer offer) {
        // don't count inactive loans-- they were pre-approved rather than expired
        // I'm just using expired to catch them for deletion
        if (offer.getIsActiveOffer())
            myLP.offerExpired();
    }

    public void cancelMortgage(Mortgage m) {
        synchronized (mortgages) {
            mortgages.remove(m);
        }
    }

     public synchronized void offerAccepted(MortgageOffer offer) {
        // inactive loans  were pre-approved
        // nothing to do here
     }

    private void preApproveMortgageMessage(MortgageOffer offer, iBuyerSeller borrower) {
        // record that we've loaned out the money
        // decrease the number of offers out
        double loanAmount = offer.getAmountToBorrrow();
        myBooks.transaction(loanAmount, BankBooks.Transaction.LOANPREAPPROVAL);
        // generate mortgage to give to borrower
        Mortgage mortgage = new Mortgage(this, (Household) borrower, loanAmount, offer.getTerms(), true, 120);
        // add to my list of mortgages
        addMortgage(mortgage);
        
        // send acount number and mortgage to household
        borrower.mortgagePreapproval(mortgage);
    }

    // buyer has pre-approved mortgage and is ready to buy a specfiic house
    private void requestFundingMessage(Mortgage mortgage, double requestedAmount) {
        throttleLoanOffers();
        if ((requestedAmount <= mortgage.getPreapprovedAmount()) && (myBooks.canFundMortgage(requestedAmount))) {
            myBooks.transaction(requestedAmount, BankBooks.Transaction.FUNDMORTGAGE);
            mortgage.hereIsFunding(requestedAmount);
        } else
            mortgage.hereIsFunding(0);
    }


    // borrower is making a mortgage payment, but the mortgage sends it to the lender
    // because the mortgage may be sold, and the borrower does not need to know who now
    // holds the mortgage
    public synchronized void payment(double amount, double principal) {
        // the mortgage casj is handled by the mortgage object
        // the bank just needs to account for the income
        if ((amount < 0) || (principal < 0)) {
            boolean wawaoo = true;
        }
        myBooks.transaction(principal, BankBooks.Transaction.MORTGAGEPRINCIPAL);
        myBooks.transaction(amount - principal, BankBooks.Transaction.MORTGAGEINTEREST);
        //loanBalanceOutstanding -= principal;
     }

    public void missedPayment() {
        myBooks.transaction(0, BankBooks.Transaction.MORTGAGEDEFAULT);
    }

      public synchronized void payPoints(double amount) {
          // pure profit for the bank!
          myBooks.transaction(amount, BankBooks.Transaction.POINTSPAYMENT);
      }


    // the borrower can no longer aford the mortage,and will stop paying
    // the lender gets the house
    public void defaultOnMortgage(int account) {
        // destroy mortgage
        // take possession of house
        // record default in borower's credit record
    }

    // the mortgage is completely paid off
    public synchronized void paidInFull() {
        // mortgage is paid off, destroy mortgage
        // mortgage knows it's been paid off, and will tell borrower
        myBooks.transaction(0, BankBooks.Transaction.MORTGAGEPAIDOFF);
    }

    //
    // Savings
    //
    // Depository day in the life
    // compute interest on accounts
    private void computeMonthlyInterest() {
        if (dayOfMonth != myDayOfMonth) return;
        for(DepositoryAccount d : savingsAccounts) {
            if (d.depositor != null) {
                double interest = d.balance * savingsMonthlyInterestRate;
                d.balance += interest;
                d.depositor.interestPaidThisPeriod(interest);
                myBooks.transaction(interest, BankBooks.Transaction.SAVINGSINTEREST);
            }
        }
    }

    // iDepository Interface
    // create an account and return the account number
    public int openAccount(iDepositor depositor, double amount) {
        if (amount < 0)
            return kOpenAccountFailed;
        // allow depositors to have multiple accounts
        // create a new DepositoryAccount object
        DepositoryAccount newAccount = new DepositoryAccount();
        maxSavingsAccountNumberAssigned += 1;
        // we are using the account number as an index into the list of savings accounts
        // we don't remove closed accounts from the list, nor reuse closed account numbers
        newAccount.accountNumber = maxSavingsAccountNumberAssigned;
        newAccount.balance = amount;
        newAccount.depositor = depositor;
        newAccount.interestRate = savingsMonthlyInterestRate;
        savingsAccounts.add(newAccount);
        myBooks.transaction(amount, BankBooks.Transaction.DEPOSIT);
        return newAccount.accountNumber;
    }

    // Add deposit to depositor's account referenced by account number
    public synchronized void deposit(int accountNumber, double amount) {
        DepositoryAccount account = savingsAccounts.get(accountNumber);
        account.balance += amount;
        myBooks.transaction(amount, BankBooks.Transaction.DEPOSIT);
    }

    // Add deposit to depositor's account referenced by account number
    public synchronized double withdraw(int accountNumber, double amount) {
        DepositoryAccount account = savingsAccounts.get(accountNumber);
        if (account.balance < amount)
            return 0;
        else {
            account.balance -= amount;
            myBooks.transaction(amount, BankBooks.Transaction.WITHDRAWL);
            return amount;
        }
    }

    // return the amount of money in the acount
    public double getBalance(int accountNumber) {
        DepositoryAccount account = savingsAccounts.get(accountNumber);
        return account.balance;
    }

    // close the acount and return the cash to the depositor
    public double closeAccount(int accountNumber) {
        DepositoryAccount account = savingsAccounts.get(accountNumber);
        double balance = account.balance;
        account.close();
        myBooks.transaction(balance, BankBooks.Transaction.WITHDRAWL);
        account.depositor = null;
        return balance;
    }

    
    //
    // iBuyerSeller Interface becaue a bank may need to sell a foreclosed house
    //
    public void houseFound(House house) {
        // Banks dont buy houses
    }
    public void buyerFound(iBuyerSeller buyer) {

    }
    public void payCashForHouse(iBuyerSeller buyer, House house, double payment) {
        // Banks dont buy houses
    }
    public void hereIsTitle(House house, Title title) {
        // Banks dont buy houses
    }
    public synchronized void mortgagePreapproval(Mortgage mortgage) {
        // Banks dont buy houses
    }
    private void mortgageFundedMessage(Mortgage mortgage) {
         // buy house; will get title in return
    }
    public synchronized void preAppMortgageExpired(Mortgage m) {
        // Banks dont buy houses
    }
    public synchronized void mortgagePaidInFull(Mortgage m) {
        // Banks dont buy houses
    }

    //
    // iGoverned Interface
    //
    public void updateRegsAndExternals(Regulations regs, Externals ext) {

    }


    //
    // Actions
    //
    public static final Swarm.SwarmAction<Bank> computeMonthlyInterest = new Swarm.SwarmAction<Bank>() {

        public synchronized void exec(Bank bank) {
            bank.computeMonthlyInterest();
        }
    };
    public static final Swarm.SwarmAction<Bank> newDay = new Swarm.SwarmAction<Bank>() {

        public synchronized void exec(Bank bank) {
            bank.newDay();
        }
    };
    public static final Swarm.SwarmAction<Bank> offerMortgages = new Swarm.SwarmAction<Bank>() {

        public synchronized void exec(Bank bank) {
            bank.offerMortgages();
        }
    };
    public static final Swarm.SwarmAction<Bank> agePreapprovedMortgages = new Swarm.SwarmAction<Bank>() {

        public synchronized void exec(Bank bank) {
            bank.agePreapprovedMortgages();
        }
    };

    //
    // messages
    //
    public Action preApproveMortgage(final MortgageOffer offer, final iBuyerSeller borrower) {
        return new Action() {

            public synchronized void exec() {
                preApproveMortgageMessage(offer, borrower);
            }
        };
    }

    public Action mortgageFunded(final Mortgage mortgage) {
            return new Action() { public synchronized void exec() {
                 mortgageFundedMessage(mortgage);
             }
         };
     }

     public Action requestFunding(final Mortgage m, final double amt) {
            return new Action() { public synchronized void exec() {
                 requestFundingMessage(m, amt);
             }
         };
     }


    //
    //Metrics
    //
    public static List<SwarmMetric<Bank>> getMetrics() {
        List<SwarmMetric<Bank>> rval = new ArrayList<SwarmMetric<Bank>>();

        SwarmMetric<Bank> metric;

        //
        metric = new SwarmMetric<Bank>("Equity", Metric.Units.Dollars,
                new Compute<Bank>() {
                    public double exec(Bank obj) {
                        return obj.getEquity();
                    }
                });
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("Available to Loan", Metric.Units.Dollars,
                new Compute<Bank>() {
                    public double exec(Bank obj) {
                        return obj.myBooks.getAmountAvailableToLoan();
                    }
                });
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("Deposits", Metric.Units.Dollars,
                new Compute<Bank>() {
                    public double exec(Bank obj) {
                        return obj.getDeposits();
                    }
                });
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("Loans Outstanding", Metric.Units.Dollars,
                new Compute<Bank>() {
                    public double exec(Bank obj) {
                        return obj.getLoanBalanceOutstanding();
                    }
                });
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("Bank Cash Assets", Metric.Units.Dollars,
                new Compute<Bank>() {
                    public double exec(Bank obj) {
                        return obj.getCashAssets();
                    }
                });
        // do not average values over banks
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("# Loans funded for quarter", Metric.Units.Num,
                new Compute<Bank>() {

                    public double exec(Bank obj) {
                        return obj.getNumLoansFundedThisQtr();
                    }
                });
        metric.turnOffAveraging();
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        //
        metric = new SwarmMetric<Bank>("Avg Loan Size", Metric.Units.Dollars,
                new Compute<Bank>() {

                    public double exec(Bank obj) {
                        return obj.getAverageLoanSizeThisQtr();
                    }
                });
        metric.setNumericalRange(new double[]{0, 1000, 5000, 10000, 20000, 50000, 80000, 100000});
        rval.add(metric);

        return rval;
    }

}
